package store.lunangangster.engine.impl.config;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import store.lunangangster.engine.EngineClock;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.ProcessEngine;
import store.lunangangster.engine.ProcessEngineConfiguration;
import store.lunangangster.engine.bpmn.parser.BpmnParsers;
import store.lunangangster.engine.bpmn.parser.handler.BoundaryEventParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.BpmnParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.BpmnParseHandlers;
import store.lunangangster.engine.bpmn.parser.handler.EndEventParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.ExclusiveGatewayParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.InclusiveGatewayParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.MessageEventDefinitionParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.ParallelGatewayParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.ProcessParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.SequenceFlowParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.StartEventParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.SubProcessParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.TaskParseHandler;
import store.lunangangster.engine.bpmn.parser.handler.UserTaskParseHandler;
import store.lunangangster.engine.impl.Context;
import store.lunangangster.engine.impl.ProcessEngineClock;
import store.lunangangster.engine.impl.ProcessEngineImpl;
import store.lunangangster.engine.impl.agenda.DefaultAgendaFactory;
import store.lunangangster.engine.impl.agenda.NodeFlowEngineAgendaFactory;
import store.lunangangster.engine.impl.calendar.BusinessCalendar;
import store.lunangangster.engine.impl.calendar.CycleCalendar;
import store.lunangangster.engine.impl.calendar.DateCalendar;
import store.lunangangster.engine.impl.calendar.DefaultCalendarManager;
import store.lunangangster.engine.impl.calendar.DurationCalendar;
import store.lunangangster.engine.impl.cmd.CommandExecutor;
import store.lunangangster.engine.impl.cmd.CommandExecutorImpl;
import store.lunangangster.engine.impl.db.DbIdGenerator;
import store.lunangangster.engine.impl.db.DbSqlSessionFactory;
import store.lunangangster.engine.impl.db.MybatisStatements;
import store.lunangangster.engine.impl.db.cache.DefaultEntityCacheSessionFactory;
import store.lunangangster.engine.impl.db.cache.EntityCacheSessionFactory;
import store.lunangangster.engine.impl.el.ExpressManager;
import store.lunangangster.engine.impl.entity.mgr.ByteArrayManager;
import store.lunangangster.engine.impl.entity.mgr.ByteArrayManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.DeploymentEntityManager;
import store.lunangangster.engine.impl.entity.mgr.DeploymentEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.DeploymentManager;
import store.lunangangster.engine.impl.entity.mgr.ExecutionEntityManager;
import store.lunangangster.engine.impl.entity.mgr.ExecutionEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.HistoryEntityManager;
import store.lunangangster.engine.impl.entity.mgr.HistoryEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.HistoryManager;
import store.lunangangster.engine.impl.entity.mgr.IdentityLinkEntityManager;
import store.lunangangster.engine.impl.entity.mgr.IdentityLinkEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.MesListenerEntityManager;
import store.lunangangster.engine.impl.entity.mgr.MesListenerEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.ProcessDefinitionEntityManager;
import store.lunangangster.engine.impl.entity.mgr.ProcessDefinitionEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.ResourceEntityManager;
import store.lunangangster.engine.impl.entity.mgr.ResourceEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.TaskEntityManager;
import store.lunangangster.engine.impl.entity.mgr.TaskEntityManagerImpl;
import store.lunangangster.engine.impl.entity.mgr.VariableEntityManager;
import store.lunangangster.engine.impl.entity.mgr.VariableEntityManagerImpl;
import store.lunangangster.engine.impl.helper.ProcessInstanceHelper;
import store.lunangangster.engine.impl.interceptor.CommandContextInterceptor;
import store.lunangangster.engine.impl.interceptor.CommandInterceptor;
import store.lunangangster.engine.impl.interceptor.CommandInvoker;
import store.lunangangster.engine.impl.interceptor.TransactionInterceptor;
import store.lunangangster.engine.impl.repository.BpmnDeployer;
import store.lunangangster.engine.impl.repository.DefaultDeploymentCache;
import store.lunangangster.engine.impl.repository.DeployCacheManager;
import store.lunangangster.engine.impl.repository.DeploymentCache;
import store.lunangangster.engine.impl.repository.DeploymentParseBuilderFactory;
import store.lunangangster.engine.impl.repository.ProcessDefinitionCacheEntry;
import store.lunangangster.engine.impl.service.HistoryServiceImpl;
import store.lunangangster.engine.impl.service.RepositoryServiceImpl;
import store.lunangangster.engine.impl.service.RuntimeServiceImpl;
import store.lunangangster.engine.impl.service.TaskServiceImpl;
import store.lunangangster.engine.impl.util.ReflectUtil;
import store.lunangangster.engine.service.HistoryService;
import store.lunangangster.engine.service.RepositoryService;
import store.lunangangster.engine.service.RuntimeService;
import store.lunangangster.engine.service.TaskService;

public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfiguration {

  private static final Logger log = LoggerFactory.getLogger(ProcessEngineConfigurationImpl.class);

  protected static final String DEFAULT_MYBATIS_CONFIG = "/store/lunangangster/db/ibatis/mybatis-config.xml";

  // TransactionFactory ############################
  protected TransactionFactory transactionFactory;

  // Entity Manager ############################
  protected DeploymentEntityManager deploymentEntityManager;
  protected ResourceEntityManager resourceEntityManager;
  protected ProcessDefinitionEntityManager processDefinitionEntityManager;
  protected ExecutionEntityManager executionEntityManager;
  protected TaskEntityManager taskEntityManager;
  protected IdentityLinkEntityManager identityLinkEntityManager;
  protected VariableEntityManager variableEntityManager;
  protected MesListenerEntityManager mesListenerEntityManager;
  protected HistoryEntityManager historyEntityManager;
  protected ByteArrayManager byteArrayManager;

  // Helper ############################
  protected ProcessInstanceHelper processInstanceHelper;
  protected EngineClock engineClock;

  // Deployment ###################################
  protected DeploymentCache<ProcessDefinitionCacheEntry> deploymentCache;
  protected DeployCacheManager deployCacheManager;
  protected ExpressManager expressManager;
  protected BpmnParsers bpmnParsers;
  protected DeploymentParseBuilderFactory deploymentParseBuilderFactory;
  protected BpmnDeployer bpmnDeployer;
  protected DeploymentManager deploymentManager;

  // Managers ###################################
  protected HistoryManager historyManager;
  protected DefaultCalendarManager calendarManager;

  // SessionFactory ############################
  protected SqlSessionFactory sqlSessionFactory;
  protected EntityCacheSessionFactory entityCacheSessionFactory;
  protected DbSqlSessionFactory dbSqlSessionFactory;

  // AgendaFactory ############################
  protected NodeFlowEngineAgendaFactory agendaFactory;

  // CommandExecutor ###########################
  protected CommandExecutor executor;

  // Services ##################################
  protected RepositoryService repositoryService;
  protected RuntimeService runtimeService;
  protected TaskService taskService;
  protected HistoryService historyService;

  protected boolean isInitialized = false;

  @Override
  public synchronized ProcessEngine buildEngine() {
    if (!isInitialized) {
      log.info("Nodeflow-durable Engine is initializing");
      init();
    }
    log.info("Nodeflow-durable Engine has been initialized");
    return new ProcessEngineImpl(this);
  }

  public void init() {
    // DataSource
    initDataSource();
    // SessionFactories
    initSqlSessionFactory();
    initSessionFactories();
    // Entity Managers
    initEntityManagers();
    // Helper
    initHelper();
    // Deployment
    initDeployment();
    // Manager
    initManagers();
    // AgendaFactory
    initAgendaFactory();
    // Executors
    initCommandExecutors();
    // Services
    initServices();
    // Context
    initContext();

    isInitialized = true;
  }

  private void initAgendaFactory() {
    if (agendaFactory == null) {
      agendaFactory = new DefaultAgendaFactory();
    }
  }

  private void initDataSource() {
    if (dataSource == null) {
      if (jdbcUrl == null || jdbcUsername == null || jdbcDriverName == null) {
        throw new NodeFlowException("Jdbc Properties have to be specified");
      }
      PooledDataSource pooledDataSource = new PooledDataSource(ReflectUtil.getClassLoader(),
          jdbcDriverName, jdbcUrl, jdbcUsername, jdbcPassword);
      pooledDataSource.forceCloseAll();
      dataSource = pooledDataSource;
    }

    if (transactionFactory == null) {
      transactionFactory = new JdbcTransactionFactory();
    }
  }

  private void initEntityManagers() {
    if (deploymentEntityManager == null) {
      deploymentEntityManager = new DeploymentEntityManagerImpl(this);
    }
    if (resourceEntityManager == null) {
      resourceEntityManager = new ResourceEntityManagerImpl(this);
    }
    if (processDefinitionEntityManager == null) {
      processDefinitionEntityManager = new ProcessDefinitionEntityManagerImpl(this);
    }
    if (executionEntityManager == null) {
      executionEntityManager = new ExecutionEntityManagerImpl(this);
    }
    if (taskEntityManager == null) {
      taskEntityManager = new TaskEntityManagerImpl(this);
    }
    if (identityLinkEntityManager == null) {
      identityLinkEntityManager = new IdentityLinkEntityManagerImpl(this);
    }
    if (variableEntityManager == null) {
      variableEntityManager = new VariableEntityManagerImpl(this);
    }
    if (mesListenerEntityManager == null) {
      mesListenerEntityManager = new MesListenerEntityManagerImpl(this);
    }
    if (historyEntityManager == null) {
      historyEntityManager = new HistoryEntityManagerImpl(this);
    }
    if (byteArrayManager == null) {
      byteArrayManager = new ByteArrayManagerImpl(this);
    }
  }

  private void initHelper() {
    if (processInstanceHelper == null) {
      processInstanceHelper = new ProcessInstanceHelper();
    }
    if (engineClock == null) {
      engineClock = new ProcessEngineClock();
    }
  }

  private void initDeployment() {
    if (deploymentCache == null) {
      deploymentCache = new DefaultDeploymentCache<>();
    }
    if (deployCacheManager == null) {
      deployCacheManager = new DeployCacheManager(this);
    }
    if (expressManager == null) {
      expressManager = new ExpressManager(this);
    }
    if (bpmnParsers == null) {
      bpmnParsers = new BpmnParsers();
      initBpmnParseHandlers(bpmnParsers);
    }
    if (deploymentParseBuilderFactory == null) {
      deploymentParseBuilderFactory = new DeploymentParseBuilderFactory(this);
    }
    if (bpmnDeployer == null) {
      bpmnDeployer = new BpmnDeployer(this);
    }
    if (deploymentManager == null) {
      deploymentManager = new DeploymentManager(this);
    }
  }

  private void initManagers() {
    if (historyManager == null) {
      historyManager = new HistoryManager(this);
    }
    if (calendarManager == null) {
      calendarManager = new DefaultCalendarManager(this, initCalendars());
    }
  }

  private Map<String, BusinessCalendar> initCalendars() {
    Map<String, BusinessCalendar> map = new HashMap<>();
    map.put(DateCalendar.name, new DateCalendar());
    map.put(DurationCalendar.name, new DurationCalendar());
    map.put(CycleCalendar.name, new CycleCalendar());

    return map;
  }

  private void initBpmnParseHandlers(final BpmnParsers bpmnParsers) {
    BpmnParseHandlers bpmnParseHandlers = new BpmnParseHandlers();
    bpmnParseHandlers.addHandlers(getDefaultBpmnParseHandlers());
    bpmnParsers.setBpmnParseHandlers(bpmnParseHandlers);
  }

  private List<BpmnParseHandler> getDefaultBpmnParseHandlers() {
    return Arrays.asList(
        // Process
        new ProcessParseHandler(),
        // Gateway
        new ExclusiveGatewayParseHandler(),
        new InclusiveGatewayParseHandler(),
        new ParallelGatewayParseHandler(),
        // Activity
        new TaskParseHandler(),
        new UserTaskParseHandler(),
        new SubProcessParseHandler(),
        // Event
        new StartEventParseHandler(),
        new EndEventParseHandler(),
        new BoundaryEventParseHandler(),
        // EventDefinition
        new MessageEventDefinitionParseHandler(),
        // SequenceFlow
        new SequenceFlowParseHandler()
    );
  }

  private void initSqlSessionFactory() {
    // 读取mybatis-config.xml文件
    InputStream is = ProcessEngineConfigurationImpl.class.getResourceAsStream(DEFAULT_MYBATIS_CONFIG);
    Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
    Environment environment = new Environment("development", transactionFactory, dataSource);
    XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
    Configuration configuration = xmlConfigBuilder.parse();
    configuration.setEnvironment(environment);
    // Configuration添加Mappers
    // configuration.addMappers();
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
  }

  private void initSessionFactories(){
    entityCacheSessionFactory = new DefaultEntityCacheSessionFactory();
    dbSqlSessionFactory = DbSqlSessionFactory.newInstance(sqlSessionFactory, new MybatisStatements(), new DbIdGenerator(), this);
  }

  private void initCommandExecutors() {
    CommandInterceptor interceptorsHead = initDefaultCommandInterceptors();
    executor = new CommandExecutorImpl();
    executor.setHead(interceptorsHead);
  }

  private CommandInterceptor initDefaultCommandInterceptors() {
    CommandInvoker commandInvoker   // tail
        = new CommandInvoker();
    TransactionInterceptor transactionInterceptor
        = new TransactionInterceptor(commandInvoker);
    CommandContextInterceptor commandContextInterceptor   // head
        = new CommandContextInterceptor(transactionInterceptor);

    return commandContextInterceptor;
  }

  private void initContext() {
    Context.setProcessEngineConfiguration(this);
  }

  private void initServices() {
    repositoryService = new RepositoryServiceImpl(this);
    runtimeService = new RuntimeServiceImpl(this);
    taskService = new TaskServiceImpl(this);
    historyService = new HistoryServiceImpl(this);
  }


  @Override
  public RepositoryService getRepositoryService() {
    return repositoryService;
  }

  @Override
  public RuntimeService getRuntimeService() {
    return runtimeService;
  }

  @Override
  public TaskService getTaskService() {
    return taskService;
  }

  @Override
  public HistoryService getHistoryService() {
    return historyService;
  }

  @Override
  public DeploymentEntityManager getDeploymentEntityManager() {
    return deploymentEntityManager;
  }

  @Override
  public ResourceEntityManager getResourceEntityManager() {
    return resourceEntityManager;
  }

  @Override
  public ProcessDefinitionEntityManager getProcessDefinitionEntityManager() {
    return processDefinitionEntityManager;
  }

  @Override
  public ExecutionEntityManager getExecutionEntityManager() {
    return executionEntityManager;
  }

  @Override
  public TaskEntityManager getTaskEntityManager() {
    return taskEntityManager;
  }

  @Override
  public IdentityLinkEntityManager getIdentityLinkEntityManager() {
    return identityLinkEntityManager;
  }

  @Override
  public VariableEntityManager getVariableEntityManager() {
    return variableEntityManager;
  }

  @Override
  public MesListenerEntityManager getMesListenerEntityManager() {
    return mesListenerEntityManager;
  }

  @Override
  public HistoryEntityManager getHistoryEntityManager() {
    return historyEntityManager;
  }

  @Override
  public ByteArrayManager getByteArrayManager() {
    return byteArrayManager;
  }

  @Override
  public ProcessInstanceHelper getProcessInstanceHelper() {
    return processInstanceHelper;
  }

  @Override
  public DeploymentManager getDeploymentManager() {
    return deploymentManager;
  }

  @Override
  public BpmnDeployer getBpmnDeployer() {
    return bpmnDeployer;
  }

  @Override
  public DeploymentParseBuilderFactory getDeploymentParseBuilderFactory() {
    return deploymentParseBuilderFactory;
  }

  @Override
  public DeployCacheManager getDeployCacheManager() {
    return deployCacheManager;
  }

  @Override
  public HistoryManager getHistoryManager() {
    return historyManager;
  }

  @Override
  public ExpressManager getExpressManager() {
    return expressManager;
  }

  @Override
  public EntityCacheSessionFactory getEntityCacheSessionFactory() {
    return entityCacheSessionFactory;
  }

  @Override
  public TransactionFactory getTransactionFactory() {
    return transactionFactory;
  }

  @Override
  public DbSqlSessionFactory getDbSqlSessionFactory() {
    return dbSqlSessionFactory;
  }

  @Override
  public NodeFlowEngineAgendaFactory getAgendaFactory() {
    return agendaFactory;
  }

  @Override
  public CommandExecutor getCommandExecutor() {
    return executor;
  }

  @Override
  public EngineClock getEngineClock() {
    return engineClock;
  }

  @Override
  public BpmnParsers getBpmnParser() {
    return bpmnParsers;
  }

  @Override
  public DeploymentCache<ProcessDefinitionCacheEntry> getDeploymentCache() {
    return deploymentCache;
  }

  @Override
  public DefaultCalendarManager getCalendarManager() {
    return calendarManager;
  }
}
